/**
 * 
 */
package net.phoenix.repository.query.lucene;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.Workspace;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.QueryResult;
import javax.jcr.query.Row;
import javax.jcr.query.RowIterator;

import net.phoenix.repository.BasicWorkspace;
import net.phoenix.repository.RepositoryRuntimeException;
import net.phoenix.repository.query.GeneralQuery;

import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopScoreDocCollector;

/**
 * @author lixf
 * 
 */
public class LuceneQuery extends GeneralQuery implements Constants {

	private LuceneQueryConfig config;
	private Map<String, String> parameters;
	private IndexSearcher searcher;
	private TopScoreDocCollector scoreDocCollector;

	public LuceneQuery() {
	
	}
	public void init(Workspace workspace, String statement) throws InvalidQueryException {
		super.init(workspace, statement);
		this.config = (LuceneQueryConfig)repository.getInitParameter("queryConfig");
		this.parameters = new HashMap<String, String>();
		this.searcher = null;
	}


	protected void setStatement(String statement) {
		this.statement = statement;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.query.Query#setLimit(long)
	 */
	@Override
	public void setLimit(long limit) {
		this.limit = limit;

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.query.Query#setOffset(long)
	 */
	@Override
	public void setOffset(long offset) {
		this.offset = offset;
	}

	public long getLimit() {
		return limit;
	}

	public long getOffset() {
		return offset;
	}

	public BasicWorkspace getWorkspace() {
		return workspace;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.query.Query#getStatement()
	 */
	@Override
	public String getStatement() {
		return this.statement;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.query.Query#getLanguage()
	 */
	@Override
	public String getLanguage() {
		return JCR_SQL2;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.query.Query#bindValue(java.lang.String, javax.jcr.Value)
	 */
	@Override
	public void bindValue(String varName, Value value) throws IllegalArgumentException, RepositoryException {
		this.parameters.put(varName, value.getString());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.query.Query#getBindVariableNames()
	 */
	@Override
	public String[] getBindVariableNames() throws RepositoryException {
		String[] result = new String[this.parameters.size()];
		for (int i = 0; i < result.length; i++)
			result[i] = "" + i;
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.query.Query#execute()
	 */
	@Override
	public QueryResult execute() throws InvalidQueryException, RepositoryException {
		String statement = StrSubstitutor.replace(this.statement, this.parameters);
		try {
			this.searcher = config.createSearcher();
			Query query = config.getQueryParser().parse(statement);
			scoreDocCollector = TopScoreDocCollector.create(searcher.maxDoc(), false);
			searcher.search(query, scoreDocCollector);
			return new LuceneQueryResult();
		} catch (IOException e) {
			throw new RepositoryException(e);
		} catch (ParseException e) {
			throw new RepositoryException(e);
		}
	}

	class LuceneQueryResult implements QueryResult {
		private String[] columns;
		private long size;

		public LuceneQueryResult() throws RepositoryException, CorruptIndexException, IOException {
			this.size = scoreDocCollector.getTotalHits();
			this.columns = null;
		}

		@Override
		public String[] getColumnNames() throws RepositoryException {
			if (size > 0 && this.columns == null) {
				try {
					Document document = searcher.doc(scoreDocCollector.topDocs(0, 1).scoreDocs[0].doc);
					List<Fieldable> fields = document.getFields();
					this.columns = new String[fields.size()];
					for (int i = 0; i < fields.size(); i++) {
						this.columns[i] = fields.get(i).name();
					}
				} catch (CorruptIndexException e) {
					throw new RepositoryException(e);
				} catch (IOException e) {
					throw new RepositoryException(e);
				}
			}

			return this.columns;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.jcr.query.QueryResult#getRows()
		 */
		@Override
		public RowIterator getRows() throws RepositoryException {
			return new LuceneRowIterator();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.jcr.query.QueryResult#getNodes()
		 */
		@Override
		public NodeIterator getNodes() throws RepositoryException {
			return new LuceneNodeIterator();
		}

		@Override
		public String[] getSelectorNames() throws RepositoryException {
			return this.columns;
		}

		class LuceneRowIterator implements RowIterator {
			private int position;
			private ScoreDoc[] scoreDocs;
			private int limit;

			public LuceneRowIterator() {
				this.position = -1;
				this.scoreDocs = scoreDocCollector.topDocs((int) offset, (int) limit).scoreDocs;
				this.limit = (limit < scoreDocs.length ? limit : scoreDocs.length);
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see javax.jcr.RangeIterator#skip(long)
			 */
			@Override
			public void skip(long skipNum) {
				this.position += (int) skipNum;
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see javax.jcr.RangeIterator#getSize()
			 */

			@Override
			public long getSize() {
				return scoreDocCollector.getTotalHits();
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see javax.jcr.RangeIterator#getPosition()
			 */
			@Override
			public long getPosition() {
				return this.position + offset;
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see java.util.Iterator#hasNext()
			 */
			@Override
			public boolean hasNext() {
				return this.position < this.limit - 1;
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see java.util.Iterator#next()
			 */
			@Override
			public Object next() {
				return this.nextRow();
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see java.util.Iterator#remove()
			 */
			@Override
			public void remove() {
				throw new IllegalStateException();
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see javax.jcr.query.RowIterator#nextRow()
			 */
			@Override
			public Row nextRow() {
				this.position++;

				try {
					return new LuceneRow(workspace.getSession(), searcher.doc(this.scoreDocs[position].doc));
				} catch (UnsupportedRepositoryOperationException e) {
					throw new RepositoryRuntimeException(e);
				} catch (RepositoryException e) {
					throw new RepositoryRuntimeException(e);
				} catch (CorruptIndexException e) {
					throw new RepositoryRuntimeException(e);
				} catch (IOException e) {
					throw new RepositoryRuntimeException(e);
				}
			}
		}

		class LuceneNodeIterator implements NodeIterator {
			private int position;
			private ScoreDoc[] scoreDocs;
			private int count;

			public LuceneNodeIterator() {
				this.position = -1;
				this.scoreDocs = scoreDocCollector.topDocs((int) offset, (int) limit).scoreDocs;
				this.count = ((int)limit < scoreDocs.length ? (int)limit : scoreDocs.length);
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see javax.jcr.RangeIterator#skip(long)
			 */
			@Override
			public void skip(long skipNum) {
				this.position += (int) skipNum;
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see javax.jcr.RangeIterator#getSize()
			 */

			@Override
			public long getSize() {
				return scoreDocCollector.getTotalHits();
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see javax.jcr.RangeIterator#getPosition()
			 */
			@Override
			public long getPosition() {
				return this.position + offset;
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see java.util.Iterator#hasNext()
			 */
			@Override
			public boolean hasNext() {
				return this.position < this.count - 1;
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see java.util.Iterator#next()
			 */
			@Override
			public Object next() {
				return this.nextNode();
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see java.util.Iterator#remove()
			 */
			@Override
			public void remove() {
				throw new IllegalStateException();
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see javax.jcr.query.RowIterator#nextRow()
			 */
			@Override
			public Node nextNode() {
				this.position++;

				try {
					Document doc = searcher.doc(this.scoreDocs[position].doc);
					String pkid = doc.get(INDEX_IDENTIFIER);
					return workspace.getSession().getNodeByIdentifier(pkid);
				} catch (UnsupportedRepositoryOperationException e) {
					throw new RepositoryRuntimeException(e);
				} catch (RepositoryException e) {
					throw new RepositoryRuntimeException(e);
				} catch (CorruptIndexException e) {
					throw new RepositoryRuntimeException(e);
				} catch (IOException e) {
					throw new RepositoryRuntimeException(e);
				}
			}
		}
	}
}